package io.github.libkodi.basic_gateway.component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Set;

import io.github.libkodi.basic_gateway.context.Context;
import io.github.libkodi.basic_gateway.tools.ClassFilter;
import io.github.libkodi.basic_gateway.tools.ClassUtils;
import io.vertx.core.Future;
import io.vertx.core.Promise;

public class ComponentImpl {
	private Object mutex;
	private Context context;
	private HashMap<String, Object> map = new HashMap<String, Object>();
	private ClassFilter classFilter;

	public ComponentImpl(Context context) {
		this.context = context;
		this.mutex = this;
	}

	public <T> Future<T> get(Class<T> cls) {
		return get(cls.getName(), cls);
	}
	
	@SuppressWarnings("unchecked")
	public <T> Future<T> get(String key, Class<T> cls) {
		return Future.future(promise -> {
			synchronized (mutex) {
				try {
					if (map.containsKey(key)) {
						promise.complete((T) map.get(key));
					} else {
						ComponentObject com = classFilter.getComponentClass(key);
						
						if (com == null) {
							promise.fail("Invalid component key: " + key);
						} else {
							Object instance = com.getInstance();
							
							if (!com.isFromComponents()) {
								ClassUtils.injectContext(instance, context).onSuccess(v -> {
									try {
										Method method = Component.class.getDeclaredMethod("init");
										method.invoke(instance);
										
										register(key, instance).onComplete(res -> {
											if (res.succeeded()) {
												try {
													promise.complete((T) instance);
												} catch (Exception e) {
													promise.fail(e);
												}
											} else {
												promise.fail(res.cause());
											}
										});
									} catch (Exception e) {
										promise.fail(e);
									}
								}).onFailure(promise::fail);
							} else {
								register(key, instance).onComplete(res -> {
									if (res.succeeded()) {
										try {
											promise.complete((T) instance);
										} catch (Exception e) {
											promise.fail(e);
										}
									} else {
										promise.fail(res.cause());
									}
								});
							}
						}
					}
				} catch (Exception e) {
					promise.fail(e);
				}
			}
		});
	}
	
	public Future<Void> register(Class<?> cls, Object instance) {
		return register(cls.getName(), instance);
	}
	
	public Future<Void> register(String key, Object instance) {
		return Future.future(promise -> {
			synchronized (mutex) {
				if (map.containsKey(key)) {
					promise.fail(key + " already exists");
				} else {
					map.put(key, instance);
					promise.complete();
				}
			}
		});
	}
	
	public <T> Future<T> remove(Class<T> cls) {
		return remove(cls.getName(), cls);
	}

	public <T> Future<T> remove(String key, Class<T> cls) {
		return Future.future(promise -> {
			synchronized (mutex) {
				this._remove(key, promise);
			}
		});
	}
	
	public Future<Void> removeAll() {
		return Future.future(promise -> {
			synchronized (mutex) {
				Set<String> keys = map.keySet();
				
				for (String key : keys) {
					this._remove(key, promise);
					
					if (promise.tryComplete()) {
						break;
					}
				}
			}
		});
	}

	@SuppressWarnings("unchecked")
	private <T> void _remove(String key, Promise<T> promise) {
		if (!map.containsKey(key)) {
			promise.complete(null);
		} else {
			try {
				Object obj = map.remove(key);
				
				if (obj != null) {
					ComponentObject com = classFilter.removeComponent(key);
					
					if ((com != null && !com.isFromComponents()) || obj instanceof AbstractCompoent) {
						Method method = Component.class.getDeclaredMethod("destroy");
						method.invoke(obj);
					}
				}
				
				promise.complete((T) obj);
			} catch (Exception e) {
				promise.fail(e);
			}
		}
	}

	public boolean containsKey(String key) {
		synchronized (mutex) {
			return map.containsKey(key);
		}
	}
	
}
